package model.webService;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.CharsetEncoder;

import org.json.JSONException;

import com.vandorenmes.argusv6.R;

import model.OnScreen;
import model.service.JSONBehaviour;
import model.service.ServiceBehaviour;
import view.ArgusActivity;
import view.Scanner;
import view.Splash;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

public class ClientThread implements Runnable 
{
	private Command lastCommand = Command.Login;
	
	private final String TAG = ClientThread.class.getSimpleName();
	
	private static final int SERVERPORT = 1000;
	private static final String SERVER_IP = "192.168.100.166";	// 166 = Koen & 162? = Roel
//	private static final String SERVER_IP = "192.168.30.167";	// 167 = Koen & 163 = Roel
	private static final String machineName = android.os.Build.SERIAL;
	@SuppressWarnings("unused")
	private static final int BLOCKSIZE = 128;
	
	private Socket socket;

	private ArgusActivity _activity;
	
	public ClientThread(ArgusActivity splashScreen) 
	{
		_activity = splashScreen;
	}

	@Override
	public void run() 
	{
		try
        {
        	Log.d(TAG, "before creating socket");
//        	InetSocketAddress serverAddr =  new InetSocketAddress(SERVER_IP, SERVERPORT);
//        	socket = new Socket();
//        	socket.connect(serverAddr);
        	InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
			socket = new Socket(serverAddr, SERVERPORT);
			Log.d(TAG, "socket created");
			
			sendSocketCall(Command.Login);
        	
            if(socket != null)
            {
            	// keep listening
            	while(socket.isConnected() && !socket.isClosed())
            	{
            		BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            		
            		readResponse(in);
            	}
            	Log.e(TAG, "Socket disconnected");
            }
        } catch (UnknownHostException e) {
        	Log.e(TAG, e.toString());
        } catch (ConnectException e){
        	Log.e(TAG, e.toString());
        } catch (IOException e) {
        	Log.e(TAG, e.toString());
        } catch (Exception e) {
        	Log.e(TAG, e.toString());
        } 
		finally
        {
        	handleLostConnection();
        }
	}
	
	void sendSocketCall(Command command, String msg)
	{
		Log.d(TAG, String.format("sendSocketCall(%s, %s)", command.toString(), msg));
		try 
		{
			if(socket != null)
			{
				BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
				
				int cmd = command.ordinal();
				String message = String.format("%d,%s,%s|", cmd, machineName, msg);
				
				Log.i("SEND", String.format("sending message: \"%s\" (%s)", message, command.toString()));
		        
				writer.write(message);
				writer.flush();
				
				lastCommand = command;
			}
		} catch (SocketException e)
		{
			Log.e(TAG, e.toString());
			handleLostConnection();
		}
		catch (Exception e) 
		{
			Log.e(TAG, e.toString());
		}
	}

	void sendSocketCall(Command command)
	{
		sendSocketCall(command, "");
	}
	
	private void readResponse(BufferedReader in) 
	{
		try
		{
			String response = "";
			while((response = in.readLine()) != null)
			{
				Log.i(TAG, response);
				handleReceivedMessage(response);
			}
		} catch (SocketException e)
		{
			Log.e(TAG, e.toString());
			handleLostConnection();
		}catch(Exception e)
		{
			Log.e(TAG, "read: " + e.toString());
		}
	}
 
	private void handleReceivedMessage(String message) throws Exception
	{
		if(message.trim().isEmpty())
			throw new Exception(String.format("cmd: \'%s\' returned empty message", lastCommand.toString()));
		
		switch(lastCommand)
    	{
			case Login:
				logIn();
				break;
			case Logout:
				logOut();
				break;
			case AllSnippets:
			case getSnippet:
			case Subscribe:
			case ReceiveUpdate:
			default:
				Log.i(TAG, lastCommand.toString());
				translateSnippets(message);
				break;
    	}
	}
	
	private void logIn()
	{
		if(OnScreen.getInstance().getSnippets().isEmpty())
			sendSocketCall(Command.AllSnippets);
	}
	
	public void logOut() 
	{
		sendSocketCall(Command.Logout);
		
		socket = null;
	}
	
	public void subscribeToSnippet(String snippetID)
	{
		sendSocketCall(Command.Subscribe, snippetID);
	}
		
	public void unSubscribeToSnippet(String snippetID)
	{
		sendSocketCall(Command.Unsubscribe, snippetID);
	}
		
	private void translateSnippets(String message)
	{
		Log.d(TAG, "Translate()");
		
		ServiceBehaviour translator = createTranslator(message);
		try 
		{
			translator.translateResponse(message);
			if(lastCommand == Command.AllSnippets)
				exitSplashScreen();
			lastCommand = Command.ReceiveUpdate;
		} catch (JSONException e) 
		{
			Log.e(TAG, "Invalid JSON");
			
        	_activity.makeToast(R.string.invalid_data);
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
	
	private void exitSplashScreen()
	{			
		Handler handler = new Handler(Looper.getMainLooper());
		handler.post(new ViewTransition());	
	}
	
	private class ViewTransition implements Runnable
	{
		@Override
		public void run() 
		{
			Intent intent = new Intent(_activity, Scanner.class);
			_activity.startActivity(intent);
			_activity = null;
		}
	}
	
	private ServiceBehaviour createTranslator(String result)
	{
		if(result.startsWith("<")) // XML always starts with this token and JSON starts with [
		{
			// XML (temporarily says new JsonBehaviour because the translator has to be initialized in all paths
			return new JSONBehaviour();				
		}
		else
		{
			//JSON
			return new JSONBehaviour();
		}
	}

	public void getSnippet(String id) {
		sendSocketCall(Command.getSnippet, id);
		
	}

	public void handleSubscriptions(String subscribeIds, String unSubscribeIds) 
	{		
		if(!unSubscribeIds.trim().isEmpty())
		{
			Log.i(TAG, String.format("Unsubscribed: %s", unSubscribeIds));
			sendSocketCall(Command.Unsubscribe, unSubscribeIds);
		}
		
		if(!subscribeIds.trim().isEmpty())
		{
			Log.i(TAG, String.format("Subscribed: %s", subscribeIds));
			sendSocketCall(Command.Subscribe, subscribeIds);
		}
	}
	
	public void setActivity(ArgusActivity observer) 
	{
		_activity = observer;
	}
	
	private void handleLostConnection()
	{		
		Log.e(TAG, "RECONNECT");
		if(_activity != null)
			_activity.makeToast(R.string.connection_lost);
		else
			OnScreen.getInstance().notifyScreenWithToast(R.string.connection_lost);
	}
}
